package za.org.jcicada.cfgmngr.impl;

import java.io.File;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import za.org.jcicada.cfgmngr.IPhysicalClient;
import za.org.jcicada.cfgmngr.IPhysicalServer;
import za.org.jcicada.cfgmngr.IPhysicalClient.Event;
import za.org.jcicada.genss.PhysicalURN;
import za.org.jcicada.genss.SystemURN;
import za.org.jcicada.genss.messages.Result;
import za.org.jcicada.genss.messages.SuccessCode;
import za.org.jcicada.genss.sensor.SensorURN;

public class PhysicalServer implements IPhysicalServer {

	/** Class logger */
	private static final Log logger = LogFactory.getLog(PhysicalServer.class);

	private PhysicalURN rootURN = PhysicalURN.getRootURN();

	private Map<PhysicalURN, List<PhysicalURN>> childrenMap = new HashMap<PhysicalURN, List<PhysicalURN>>();
	
	private Map<PhysicalURN, Set<SystemURN>> systemsOfMap = new HashMap<PhysicalURN, Set<SystemURN>>();
	
	private Map<SystemURN, PhysicalURN> systemMap = new HashMap<SystemURN, PhysicalURN>();

	/** Set containing attached observers */
	protected Set<IPhysicalClient> observers = new HashSet<IPhysicalClient>();
	
	/** Set of all registered views that this subject knows about. */
	protected Set<String> views = new HashSet<String>();
	
	/** Service URN for this service */
	public static final String URN_SERVICE = "physical";

	public PhysicalServer(Properties properties) {
		childrenMap.put(rootURN, new ArrayList<PhysicalURN>());
		for (Map.Entry<Object,Object> entry : properties.entrySet()) {
			try {
				SystemURN systemURN = new SystemURN(entry.getKey().toString());
				PhysicalURN physicalURN = new PhysicalURN(entry.getValue().toString());
				systemMap.put(systemURN, physicalURN);
				if (!systemsOfMap.containsKey(physicalURN)) {
					systemsOfMap.put(physicalURN, new HashSet<SystemURN>());
				}
				systemsOfMap.get(physicalURN).add(systemURN);
				addSystem(physicalURN);
			} catch (URISyntaxException ex) {
				logger.error(String.format("Can't register system %s at physical URN %s", entry.getKey(), entry.getValue()), ex);
			}
		}
		
		// find existing views
		views.addAll(lookupViews());
	}

	public boolean contains(PhysicalURN physicalURN) {
		return childrenMap.containsKey(physicalURN);
	}

	private void addSystem(PhysicalURN physicalURN) throws URISyntaxException {
		if (!childrenMap.containsKey(physicalURN)) {
			String[] path = physicalURN.getPath().substring(1).split("/");
			PhysicalURN currentURN = rootURN;
			for (String name : path) {
				PhysicalURN nextURN;
				nextURN = new PhysicalURN(String.format("%s/%s", currentURN.getPath(), name));
				List<PhysicalURN> children = childrenMap.get(currentURN);
				if (!children.contains(nextURN)) {
					childrenMap.put(nextURN, new ArrayList<PhysicalURN>());
					children.add(nextURN);
					Collections.sort(children, new Comparator<PhysicalURN>() {

						public int compare(PhysicalURN arg0, PhysicalURN arg1) {
							return arg0.toString().compareTo(arg1.toString());
						}

					});
				}
				currentURN = nextURN;
			}
		}
	}

//	private void removeSystem(PhysicalURN physicalURN) throws URISyntaxException {
//		if (childrenMap.containsKey(physicalURN)) {
//			if (childrenMap.get(physicalURN).isEmpty()) {
//				childrenMap.remove(physicalURN);
//				String[] path = physicalURN.getPath().substring(1).split("/");
//				ArrayUtils.reverse(path);
//				PhysicalURN currentURN = physicalURN;
//				for (String name : path) {
//					PhysicalURN parentURN = new PhysicalURN(currentURN.getPath().replaceFirst(String.format("/%s$", name), ""));
//					logger.info(String.format("Current = %s, Parent = %s", currentURN, parentURN));
//					List<PhysicalURN> children = childrenMap.get(parentURN);
//					logger.info(String.format("Children of parent %s = %s", parentURN, children));
//					children.remove(currentURN);
//					if (children.isEmpty()) {
//						childrenMap.remove(parentURN);
//					} else {
//						break;
//					}
//					currentURN = parentURN;
//				}
//				logger.info(String.format("Remove %s got %s", physicalURN, childrenMap.keySet()));
//			} else {
//				throw new IllegalStateException(String.format("Can't remove non-leaf %s", physicalURN));
//			}
//		}
//	}

	public List<PhysicalURN> getChildrenOf(PhysicalURN physicalURN) {
		return childrenMap.get(physicalURN);
	}
	
	public List<SystemURN> getPeerSystemsOf(SystemURN systemURN) {
		PhysicalURN physicalURN = systemMap.get(systemURN);
		List<SystemURN> systemsOfURNs = new ArrayList<SystemURN>(systemsOfMap.get(physicalURN));
		systemsOfURNs.remove(systemURN);
		return systemsOfURNs;
	}

    public void notifyViewEvent(String view, Event event) {
    	logger.info(String.format("%s %s", view, event));
    	if (event == Event.ADDED) {
    		views.add(view);
    	} 
    	for (IPhysicalClient observer : observers) {
    		observer.informViewEvent(view, event);
    	}
    	if (event == Event.REMOVED){
    		views.remove(view);
    	}
    }
    
    public Set<String> getViews() {
    	return new HashSet<String>(views);
    }
    
	public void requestGetChildren(IPhysicalClient client, PhysicalURN parentURN) {
		List<PhysicalURN> childrenURNs = getChildrenOf(parentURN);
		Result result = childrenURNs == null ? new Result(SuccessCode.COMPLETE_FAILURE, "Unrecognized parentURN") : new Result();
		client.replyGetChildren(result, parentURN, childrenURNs);
	}
	
	public void requestViewSensors(IPhysicalClient client, String view, List<PhysicalURN> physicalURNs) {
		if (view == null || physicalURNs == null) {
			String message = String.format("Received null view or physicalURN list: %s, %s", view, physicalURNs);
			logger.error(message);
			client.replyViewSensors(new Result(SuccessCode.COMPLETE_FAILURE, message), view, 0);
			return;
		}
		Properties properties = getViewProperties(view);
		if (properties == null) {
			String message = "Unknown view: " + view;
			logger.error(message);
			client.replyViewSensors(new Result(SuccessCode.COMPLETE_FAILURE, message), view, 0);
			return;
		}
		int number = 0;
		List<String> failedPhysicalURNs = new ArrayList<String>();
		for (PhysicalURN physicalURN : physicalURNs) {
			String sensorString = (String) properties.get(physicalURN.getPath());
			if (sensorString != null) {
				try {
					client.informViewSensor(view, physicalURN, new SensorURN(sensorString));
					number++;
				} catch (URISyntaxException e) {
					logger.error(e);
					failedPhysicalURNs.add(physicalURN.getPath());
				}
			} else {
				failedPhysicalURNs.add(physicalURN.getPath());
			}
		}
		Result result;
		if (number == physicalURNs.size()) {
			result = new Result();
		} else {
			result = new Result(SuccessCode.PARTIAL_SUCCESS, "Couldn't find for: " + StringUtils.join(failedPhysicalURNs, ", "));
		}
		client.replyViewSensors(result, view, number);
	}
	
	public void requestNodeSystems(IPhysicalClient client, PhysicalURN physicalURN) {
		Set<SystemURN> systemURNs = systemsOfMap.get(physicalURN);
		Result result = new Result();
		if (systemURNs == null) {
			result = new Result(SuccessCode.COMPLETE_FAILURE, String.format("Can't find systems for physicalURN: %s", physicalURN));
		}
		client.replyNodeSystems(result, physicalURN, systemURNs);
	}
	
	public void requestAttach(IPhysicalClient observer) {
		Result result;
		if (observers.contains(observer)) {
			String message = "Observer is already attached to subject";
			result = new Result(SuccessCode.PARTIAL_SUCCESS, message);
			logger.error(message);
		} else {
			result = new Result();
			observers.add(observer);
			// Inform new observer of all views
			for (String view : views) {
				observer.informViewEvent(view, Event.ADDED);
			}
		}
		observer.replyAttach(result);
	}
	
	public void requestDetach(IPhysicalClient observer) {
		Result result = detachObserver(observer);
		observer.replyDetach(result);
	}

	protected Properties getViewProperties(String view) {
		// now try to load the view
		String propertyName = CfgMngr.SYSTEM_NAME + ".view." + view;
		try {
			return PropertyLoader.loadProperties(propertyName);
		} catch (IllegalArgumentException ex) {
			return null;
		}
	}
	
	protected List<String> lookupViews() {
		Pattern pattern = Pattern.compile(".*" + File.separator + CfgMngr.SYSTEM_NAME + File.separator + "view" + File.separator + "(.*)\\.properties");
		Collection<String> resources = ResourceList.getResources(pattern);
		List<String> result = new ArrayList<String>();
		for (String resource : resources) {
			Matcher m = pattern.matcher(resource);
			if (m.find()) {
				result.add(m.group(1));
			}
		}
		return result;
	}

	public void informDisconnect(IPhysicalClient client) {
		// Do nothing
	}
	
	/**
	 * Detach observer from all systems
	 * 
	 * @param observer
	 *            to be detached from all systems
	 */
	private Result detachObserver(IPhysicalClient observer) {
		Result result;
		if (observers.contains(observer)) {
			result = new Result();
			observers.remove(observer);
		} else {
			String message = "Specified observer not currently attached";
			result = new Result(SuccessCode.PARTIAL_SUCCESS, message);
			logger.error(message);
		}
		return result;
	}

}
